(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define(factory) :
            (global.Juz580 = factory());
}(this, (function () {
    'use strict';

    function Juz580 (options) {
        if (!(this instanceof Juz580)) {
            console.warn('Juz580 is a constructor and should be called with the `new` keyword');
        }
        this._init(options);
    }

    initMiXin(Juz580);

    function initMiXin (Juz580) {
        Juz580.prototype._init = function (options) {
            var juz580 = this;
            juz580.data = options.data;
            juz580.method = options.method;
            juz580.el = options.el;
            juz580.dom = document.querySelector(juz580.el);

            /**
             * 代理data，使得访问data成员的时候可以直接使用juz580访问
             */
            proxyData(juz580);

            new Render(juz580.dom, options);

            juz580.defineReactive(juz580.data);
        }
    }


    /**
     * 代理
     * @param source 源对象
     * @param target 目标对象
     */
    function proxy (source ,target) {
        Object.keys(source).forEach(function (key) {
            Object.defineProperty(target, key, {
                enumerable: true,
                configurable: true,
                get: function () {
                    return source[key];
                },
                set: function (val) {
                    source[key] = val;
                }
            })
        });
    }
    function proxyData (juz580) {
        proxy(juz580.data,juz580);
    }


    Juz580.prototype.defineReactive = function (data) {
        var juz580 = this;
        if (typeof data === 'object') {
            Object.keys(data).forEach(function (key) {
                var val = data[key];
                dep.register(new Watcher(juz580, key, function (dom, data, key) {
                    juz580.render.parseElements();
                }));
                if (val && typeof val === 'object') {
                    juz580.defineReactive(val);
                }
                Object.defineProperty(data, key, {
                    enumerable: true,
                    configurable: true,
                    get: function () {
                        return val;
                    },
                    set: function (newVal) {
                        if (newVal === data[key]) {
                            return;
                        }
                        val = newVal;
                        if(typeof newVal === 'object'){
                            juz580.defineReactive(data[key]);
                        }
                        dep.notify();
                    }
                });
            });
        }
    };

    function Watcher (juz580, key, cb) {
        if (typeof cb !== 'function') {
            throw new Error("cb必须为一个回调函数");
        }
        this.vmdom = juz580.dom;
        this.data = juz580.data;
        this.cb = cb;

        function update () {
            //var val = data[];
            cb(this.vmdom, this.data, key);
        }

        this.update = update;
    }

    var dep = new Dep();

    function Dep () {
        var id = 0;
        var watchers = [];

        function register (watcher) {
            if (watcher instanceof Watcher) {
                if (watchers.indexOf(watcher)) {
                    watchers.push(watcher);
                } else {
                    throw new Error('该watcher重复注册');
                }
            } else {
                throw new TypeError('监听者添加失败，参数watcher类型不是Watcher');
            }
        }

        function notify () {
            watchers.forEach(function (watcher) {
                watcher.update();
            })
        }

        this.register = register;
        this.notify = notify;
    }


    function Render (dom, options) {
        this.options = options;
        this.data = options.data;
        this.dom = dom;
        this.vmdom = dom.cloneNode(true);
        var render = this;

        function init () {
            if (render.dom) {
                render.parseElements();
            } else {
                console.log('Dom元素不存在');
            }

        }

        this.parseElements = function () {
            var vdom = render.vmdom.cloneNode(true);
            var fragment = document.createDocumentFragment();
            var child = vdom.firstChild;
            while (child) {
                // 将Dom元素移入fragment中
                fragment.appendChild(child);
                child = vdom.firstChild
            }
            render.compileElement(fragment);
           // render.dom.innerText = '';
            render.dom.appendChild(fragment);
        };
        this.compileElement = function (el) {
            var childNodes = el.childNodes;
            [].slice.call(childNodes).forEach(function (node) {
                var reg = /\{\{(.*)\}\}/;
                var text = node.textContent;
                if (render.isElementNode(node)) {
                    render.compile(node);
                } else if (render.isTextNode(node) && reg.test(text)) {
                    render.compileText(node, reg.exec(text)[1]);
                }

                if (node.childNodes && node.childNodes.length) {
                    render.compileElement(node);
                }
            });
        };
        this.compileModel = function (node, data, exp, dir) {
            var val = render.getObjVal(exp);

            this.modelUpdater(node, val);

            node.addEventListener('input', function (e) {
                var newValue = e.target.value;
                if (val === newValue) {
                    return;
                }
                render.setObjVal(exp, newValue);
                val = newValue;
            });
        };

        this.compileEvent = function (node, options, exp, dir) {
            var eventType = dir.split(':')[1];
            var cb = options.methods && options.methods[exp];

            if (eventType && cb) {
                node.addEventListener(eventType, cb.bind(options), false);
            }
        };

        this.compile = function (node) {
            //事件和指令
            var nodeAttrs = node.attributes;
            Array.prototype.forEach.call(nodeAttrs, function (attr) {
                var attrName = attr.name;
                if (render.isDirective(attrName)) {
                    var attrValue = attr.value;
                    var dir = attrName.substring(2);
                    if (render.isEventDirective(dir)) {  // 事件指令
                        render.compileEvent(node, render.options, attrValue, dir);
                    } else {  // v-model 指令
                        render.compileModel(node, render.data, attrValue, dir);
                    }
                    node.removeAttribute(attrName);
                }
            });
        };

        this.compileText = function (node, exp) {
            var initText = render.getObjVal(exp);
            render.updateText(node, initText);
        };

        this.isElementNode = function (node) {
            return node.nodeType == 1;
        };

        this.isTextNode = function (node) {
            return node.nodeType == 3;
        };

        this.updateText = function (node, value) {
            node.textContent = typeof value == 'undefined' ? '' : value;
        };
        this.modelUpdater = function(node, value, oldValue) {
            node.value = typeof value == 'undefined' ? '' : value;
        };
        this.isDirective = function (attr) {
            return attr.indexOf('j-') == 0;
        };
        this.isEventDirective = function (dir) {
            return dir.indexOf('on:') === 0;
        };
        this.getObjVal = function (exp) {
            var obj = exp.split('.');
            var data = render.data;

            function each (data, keyArr, index) {
                if(typeof data === 'object'){
                   return each(data[keyArr[index]], obj, ++index);
                }else{
                    return data;
                }
            }

            return each(data, obj, 0);
        };
        this.setObjVal = function (exp, val) {
            var obj = exp.split('.');
            var data = render.data;

            function each (data, keyArr, index, val) {
                var _data = data[keyArr[index]][keyArr[index+1]];
                if(typeof _data === 'object'){
                    each( data[keyArr[index]], obj, index+1, val);
                }else{
                    data[keyArr[index]][keyArr[index+1]] = val;
                }
            }
            each(data, obj, 0 ,val)
        };
        init();
    }

    return Juz580;
})));

